#region Usings

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq.Expressions;
using SmartLib.Common;
using SmartLib.Extensions;

#endregion

namespace SmartLib.Core
{
	/// <summary>
	/// 	Base Class for Observable Objects (Models)
	/// </summary>
	public abstract class ObservableBase : IDisposable, INotifyPropertyChanged, INotifyPropertyChanging
	{
		private IDictionary<string, object> _values;

		/// <summary>
		/// 	Gets a value indicating whether this instance is disposed.
		/// </summary>
		/// <value>
		/// 	<c>true</c> if this instance is disposed; otherwise, <c>false</c>.
		/// </value>
		public virtual bool IsDisposed { get; protected set; }

		/// <summary>
		/// 	Gets or sets the values.
		/// </summary>
		/// <value>The values.</value>
		protected virtual IDictionary<string, object> Values
		{
			get { return InitValuesContainer(); }
		}

		#region IDisposable Members

		/// <summary>
		/// 	Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
		/// </summary>
		public void Dispose()
		{
			if (IsDisposed)
				throw new OperationCanceledException("This object is already disposed.");

			IsDisposed = true;
			Dispose(true);
		}

		#endregion

		#region INotifyPropertyChanged Members

		/// <summary>
		/// 	Occurs when a property value changes.
		/// </summary>
		public event PropertyChangedEventHandler PropertyChanged
		{
			add { _propertyChanged += value; }
			remove { _propertyChanged -= value; }
		}

		#endregion

		#region INotifyPropertyChanging Members

		/// <summary>
		/// 	Occurs when a property value is changing.
		/// </summary>
		public event PropertyChangingEventHandler PropertyChanging
		{
			add { _propertyChanging += value; }
			remove { _propertyChanging -= value; }
		}

		#endregion

		/// <summary>
		/// 	Occurs when [property changed].
		/// </summary>
		private event PropertyChangedEventHandler _propertyChanged;

		/// <summary>
		/// 	Occurs when [property changing].
		/// </summary>
		private event PropertyChangingEventHandler _propertyChanging;

		/// <summary>
		/// 	Inits the values container.
		/// </summary>
		/// <returns></returns>
		protected virtual IDictionary<string, object> InitValuesContainer()
		{
			return _values ?? (_values = new Dictionary<string, object>());
		}

		/// <summary>
		/// 	Releases unmanaged and - optionally - managed resources
		/// </summary>
		/// <param name = "disposing"><c>true</c> to release both managed and unmanaged resources; <c>false</c> to release only unmanaged resources.</param>
		/// <returns></returns>
		protected virtual bool Dispose(bool disposing)
		{
			if (!disposing) return false;

			_propertyChanging = null;
			_propertyChanged = null;

			if (_values != null)
			{
				lock (_values)
				{
					_values.ForEach(pair =>
					                	{
					                		if (pair.Value == null || !(pair.Value is IDisposable)) return;
					                		((IDisposable) pair.Value).Dispose();
					                	});
					_values.Clear();
					_values = null;
				}
			}
			return true;
		}

		/// <summary>
		/// 	Releases unmanaged resources and performs other cleanup operations before the
		/// 	<see cref = "ObservableBase" /> is reclaimed by garbage collection.
		/// </summary>
		~ObservableBase()
		{
			if (IsDisposed) return;
			Dispose();
		}

		/// <summary>
		/// 	Sets the value.
		/// </summary>
		/// <typeparam name = "T"></typeparam>
		/// <param name = "propertySelector">The property selector.</param>
		/// <param name = "value">The value.</param>
		protected virtual bool SetValue<T>(Expression<Func<T>> propertySelector, T value)
		{
			var propertyName = propertySelector.GetMemberName();
			var oldValue = GetValue<T>(propertyName);
			if (Equals(oldValue, value)) return false;
			OnPropertyChanging(propertyName);
			Values[propertyName] = value;
			OnPropertyChanged(propertyName);
			return true;
		}

		/// <summary>
		/// 	Sets the value.
		/// </summary>
		/// <typeparam name = "T"></typeparam>
		/// <param name = "value">The value.</param>
		/// <returns></returns>
		protected virtual bool SetValue<T>(T value)
		{
			var propertyName = new StackFrame(1, false).GetMethod().Name;
			if (propertyName.StartsWith("get_") || propertyName.StartsWith("set_"))
				propertyName = propertyName.Substring(4);

			var oldValue = GetValue<T>(propertyName);
			if (Equals(oldValue, value)) return false;
			OnPropertyChanging(propertyName);
			Values[propertyName] = value;
			OnPropertyChanged(propertyName);
			return true;
		}

		/// <summary>
		/// 	Gets the value.
		/// </summary>
		/// <typeparam name = "T"></typeparam>
		/// <returns></returns>
		protected virtual T GetValue<T>()
		{
			var propertyName = new StackFrame(1, false).GetMethod().Name;
			if (propertyName.StartsWith("get_") || propertyName.StartsWith("set_"))
				propertyName = propertyName.Substring(4);
			return GetValue<T>(propertyName);
		}

		/// <summary>
		/// 	Gets the value.
		/// </summary>
		/// <typeparam name = "T"></typeparam>
		/// <param name = "propertySelector">The property selector.</param>
		/// <returns></returns>
		protected virtual T GetValue<T>(Expression<Func<T>> propertySelector)
		{
			var propertyName = propertySelector.GetMemberName();
			return GetValue<T>(propertyName);
		}

		/// <summary>
		/// 	Gets the value.
		/// </summary>
		/// <typeparam name = "T"></typeparam>
		/// <param name = "propertyName">Name of the property.</param>
		/// <returns></returns>
		protected virtual T GetValue<T>(string propertyName)
		{
			object value;
			if (Values.TryGetValue(propertyName, out value)) return (T) value;
			return default(T);
		}

		/// <summary>
		/// 	Called when [property changed].
		/// </summary>
		/// <typeparam name = "T"></typeparam>
		/// <param name = "propertySelector">The property selector.</param>
		/// <returns></returns>
		protected virtual bool OnPropertyChanged<T>(Expression<Func<T>> propertySelector)
		{
			var propertyName = propertySelector.GetMemberName();
			return OnPropertyChanged(propertyName);
		}

		/// <summary>
		/// 	Called when [property changing].
		/// </summary>
		/// <typeparam name = "T"></typeparam>
		/// <param name = "propertySelector">The property selector.</param>
		/// <returns></returns>
		protected virtual bool OnPropertyChanging<T>(Expression<Func<T>> propertySelector)
		{
			var propertyName = propertySelector.GetMemberName();
			return OnPropertyChanging(propertyName);
		}

		/// <summary>
		/// 	Called when [property changed].
		/// </summary>
		/// <param name = "propertyName">Name of the property.</param>
		/// <returns></returns>
		protected virtual bool OnPropertyChanged(string propertyName)
		{
			if (_propertyChanged == null) return false;
			_propertyChanged(this, new PropertyChangedEventArgs(propertyName));
			return true;
		}

		/// <summary>
		/// 	Called when [property changing].
		/// </summary>
		/// <param name = "propertyName">Name of the property.</param>
		/// <returns></returns>
		protected virtual bool OnPropertyChanging(string propertyName)
		{
			if (_propertyChanging == null) return false;
			_propertyChanging(this, new PropertyChangingEventArgs(propertyName));
			return true;
		}
	}
}